﻿using EnvDTE;
using EnvDTE80;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace UTGenerator
{
    class GeradorTestes
    {
        public static void GerarTestes(object data, DTE2 dte, EnvDTE100.Solution4 sln)
        {
            try
            {
                Project projeto = SelecionarProjeto(dte);
                if (projeto == null)
                {
                    CriarProjeto(sln);
                    projeto = SelecionarProjeto(dte);
                    CriarReferencias(data, projeto);
                }

                CodeModel modelo = projeto.CodeModel;
                string nomeClasse = data.GetType().Name + "Test";
                string caminhoClasse = nomeClasse + ".cs";
                string nomeArquivoXml = SerializarObjeto(data, modelo, projeto, sln);

                CodeNamespace cns = CriarNamespace(data, modelo, caminhoClasse);

                CriarUsings(cns, data);

                CodeClass cc = CriarClasse(cns, nomeClasse, caminhoClasse);
                cc.AddAttribute("TestClass", string.Empty);

                CriarVariaveisGlobais(data, nomeArquivoXml, cc);

                MethodInfo[] metodos = data.GetType().GetMethods();
                foreach (MethodInfo metodo in metodos)
                {
                    if (!(metodo.IsSpecialName || metodo.IsSecuritySafeCritical || metodo.IsSecurityTransparent))
                    {
                        CodeFunction cf = CriarFuncao(cc, metodo.Name);
                        cf.AddAttribute("TestMethod", string.Empty);

                        StringBuilder codigoMetodo = new StringBuilder();
                        codigoMetodo.AppendLine("\t\t\t" + data.GetType().Name + " target = testObject;");

                        string parametros = string.Empty;

                        foreach (ParameterInfo parametro in metodo.GetParameters())
                        {
                            int contador = 0;
                            codigoMetodo.AppendLine("\t\t\t" + parametro.ParameterType.Name + " " + parametro.Name + " = new " + parametro.ParameterType.Name + "(); // TODO: Inicializar com um valor apropriado");
                            if (contador > 0)
                            {
                                parametros += ",";
                            }
                            parametros += parametro.Name;
                            contador++;
                        }

                        if (metodo.ReturnType.Name != "Void")
                        {
                            codigoMetodo.AppendLine("\t\t\t" + metodo.ReturnType.Name + " expected = new " + metodo.ReturnType.Name + "(); // TODO: Initialize with appropriate value");
                            codigoMetodo.AppendLine("\t\t\t" + metodo.ReturnType.Name + " actual;");
                            codigoMetodo.AppendLine("\t\t\tactual = target." + metodo.Name + "(" + parametros + ");");
                            codigoMetodo.AppendLine("\t\t\tAssert.AreEqual(expected, actual);");
                        }
                        else
                        {
                            codigoMetodo.AppendLine("\t\t\ttarget." + metodo.Name + "(" + parametros + ");");
                        }
                        codigoMetodo.AppendLine("\t\t\tAssert.Inconclusive(\"Please, check the correctness of this test method.\");");

                        cf.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint().Insert(codigoMetodo.ToString());
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        private static void CriarReferencias(object data, Project projeto)
        {
            VSLangProj.VSProject vsproj = (VSLangProj.VSProject)projeto.Object;
            vsproj.References.Add(data.GetType().Assembly.Location);

            Assembly asmXml = Assembly.LoadFile(@"refxml");
            vsproj.References.Add(asmXml.Location);
        }

        private static void CriarVariaveisGlobais(object data, string nomeArquivoXml, CodeClass cc)
        {
            StringBuilder contexto = new StringBuilder();
            contexto.AppendLine("\t\tprivate TestContext testContextInstance;");
            contexto.AppendLine("\t\tprivate static " + data.GetType().Name + " testObject;");
            contexto.AppendLine("\t\t");
            contexto.AppendLine("\t\tpublic TestContext TestContext");
            contexto.AppendLine("\t\t{");
            contexto.AppendLine("\t\t    get");
            contexto.AppendLine("\t\t    {");
            contexto.AppendLine("\t\t        return testContextInstance;");
            contexto.AppendLine("\t\t    }");
            contexto.AppendLine("\t\t    set");
            contexto.AppendLine("\t\t    {");
            contexto.AppendLine("\t\t        testContextInstance = value;");
            contexto.AppendLine("\t\t    }");
            contexto.AppendLine("\t\t}");
            contexto.AppendLine("\t\t");
            contexto.AppendLine("\t\t[ClassInitialize()]");
            contexto.AppendLine("\t\tpublic static void ClassInitializer(TestContext testContext)");
            contexto.AppendLine("\t\t{");
            contexto.AppendLine("\t\t    XmlSerializer oXmlSerializer = new XmlSerializer(typeof(" + data.GetType().Name + "));");
            contexto.AppendLine("\t\t");
            contexto.AppendLine("\t\t    string testProjectPath = System.AppDomain.CurrentDomain.BaseDirectory.Replace(@\"bin\\Debug\", \"\");");
            contexto.AppendLine("\t\t    StreamReader sr = new StreamReader(testProjectPath + \"" + nomeArquivoXml + "\");");
            contexto.AppendLine("\t\t    testObject = (" + data.GetType().Name + ")oXmlSerializer.Deserialize(sr);");
            contexto.AppendLine("\t\t}");
            contexto.AppendLine("\t\t");
            cc.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint().Insert(contexto.ToString());
        }

        private static void CriarUsings(CodeNamespace cns, object data)
        {
            System.Text.StringBuilder referencias = new System.Text.StringBuilder();
            referencias.AppendLine("using System;");
            referencias.AppendLine("using System.Collections.Generic;");
            referencias.AppendLine("using System.IO;");
            referencias.AppendLine("using System.Linq;");
            referencias.AppendLine("using System.Text;");
            referencias.AppendLine("using System.Xml;");
            referencias.AppendLine("using System.Xml.Serialization;");
            referencias.AppendLine("using Microsoft.VisualStudio.TestTools.UnitTesting;");
            referencias.AppendLine("using " + data.GetType().Assembly.GetName().Name + ";");
            referencias.AppendLine("");
            cns.GetStartPoint().CreateEditPoint().Insert(referencias.ToString());
        }

        private static Project SelecionarProjeto(DTE2 dte)
        {
            Project projeto = null;
            foreach (Project proj in dte.Solution.Projects)
            {
                if (proj.Name == "TestProject")
                {
                    projeto = proj;
                    break;
                }
            }
            return projeto;
        }

        private static void CriarProjeto(EnvDTE100.Solution4 sln)
        {
            string TemplatePath = sln.GetProjectTemplate("TestProject.zip", "CSharp");
            string projectPath = sln.FullName.Substring(0, sln.FullName.LastIndexOf(@"\")) + @"\TestProject";
            sln.AddFromTemplate(TemplatePath, projectPath, "TestProject", false);
        }

        private static CodeNamespace CriarNamespace(object data, CodeModel modelo, string caminhoClasse)
        {
            return modelo.AddNamespace("TestProject", caminhoClasse, 0);
        }

        private static string SerializarObjeto(object data, CodeModel modelo, Project projeto, EnvDTE100.Solution4 sln)
        {
            string arquivoModeloXml = sln.GetProjectItemTemplate("XMLFile.zip", "CSharp");
            //XmlTypeMapping oXmlTypeMapping = (new SoapReflectionImporter().ImportTypeMapping(data.GetType()));
            XmlSerializer serializador = new XmlSerializer(data.GetType());
            StringWriter writer = new StringWriter();
            serializador.Serialize(writer, data);
            string caminhoClasse = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
            projeto.ProjectItems.AddFromTemplate(arquivoModeloXml, caminhoClasse);
            foreach (ProjectItem pi in projeto.ProjectItems)
            {
                if (pi.Name == caminhoClasse)
                {
                    TextDocument txt = (TextDocument)pi.Document.Object("TextDocument");
                    txt.ReplacePattern("<?xml version=\"1.0\" encoding=\"utf-8\" ?>", "");
                    txt.CreateEditPoint().Insert(writer.ToString());
                    break;
                }
            }
            writer.Close();
            return caminhoClasse;
        }

        private static CodeClass CriarClasse(CodeNamespace cns, string nomeClasse, string caminhoClasse)
        {
            object[] bases = { };
            object[] interfaces = { };
            
            return cns.AddClass(nomeClasse, caminhoClasse, bases, interfaces, vsCMAccess.vsCMAccessPublic);
        }

        private static CodeFunction CriarFuncao(CodeClass cc, string nome)
        {
            CodeFunction cf = cc.AddFunction(nome + "Test", vsCMFunction.vsCMFunctionFunction, vsCMTypeRef.vsCMTypeRefVoid, 0, vsCMAccess.vsCMAccessPublic);

            return cf;
        }
    }
}
